<!DOCTYPE html>
<html lang="en">
  <head>
    <title>TapBall - p2.js canvas game for mobile</title>
    <meta charset="utf-8">

    <!-- No zooming, and device width viewport -->
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

    <!-- include the physics library -->
    <script src="../../build/p2.js"></script>

    <style>

      /* Full screen canvas */
      canvas, html, body {
        width: 100%;
        height: 100%;
        text-align: center;
        margin: 0;
        padding: 0;
      }

      /* Counter div at top */
      #counter {
        width: 100%;
        position: fixed;
        top: 0;
        font-family: "Comic Sans MS";
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }
    </style>
  </head>
  <body>

    <div id="counter">Tap ball to start</div>

    <script>

      // Globals
      var canvas, ctx, w, h, zoom, world, ballBody, bottomPlaneBody;
      var ballRadius = 1, gameWidth = 6, gameHeight = 8;

      // Initialize and start rendering the game!
      init();
      resetGame();
      requestAnimationFrame(animate);

      // Initializes canvas, physics and input events
      function init(){

        // Init canvas element and add it to the DOM
        canvas = document.createElement("CANVAS");
        w = canvas.width = window.innerWidth * window.devicePixelRatio;
        h = canvas.height = window.innerHeight * window.devicePixelRatio;
        document.body.appendChild(canvas);

        ctx = canvas.getContext("2d");
        ctx.lineWidth = 0.05;
        ctx.fillStyle = "white";

        // Create a physics world
        world = new p2.World();

        // Turn off friction and set some bounciness
        world.defaultContactMaterial.friction = 0;
        world.defaultContactMaterial.restitution = 0.5;

        // Create a physics body for the ball
        ballBody = new p2.Body({
          mass: 1,
          position: [0, -2]
        });
        ballBody.addShape(new p2.Circle({ // Give it a circle shape
          radius: ballRadius
        }));
        world.addBody(ballBody);

        // Add physics planes on the sides
        bottomPlaneBody = createPlane([0, -gameHeight/2], 0);
        createPlane([0, gameHeight/2 + ballRadius*2], Math.PI); // Top
        createPlane([-gameWidth/2, 0], -Math.PI / 2); // Left
        createPlane([gameWidth/2, 0], Math.PI / 2); // Right

        // Game over when the ball touches the bottom plane
        world.on('beginContact', function(evt){
          if((evt.bodyA === ballBody && evt.bodyB === bottomPlaneBody) || evt.bodyA === bottomPlaneBody && evt.bodyB === ballBody){
            resetGame();
          }
        });

        // Transform the canvas
        // Note that we need to flip the y axis since Canvas pixel coordinates
        // goes from top to bottom, while physics does the opposite.
        ctx.save();
        ctx.translate(w/2, h/2);  // Translate to the center
        zoom = w < h ? w/gameWidth : h/gameHeight;
        ctx.scale(zoom, -zoom); // Zoom in and flip y axis

        // Add mouse event listeners
        canvas.addEventListener('mousedown', onKeyDown);
        canvas.addEventListener('touchstart', onKeyDown);
      }

      var inputType;
      function onKeyDown(event){
        if(inputType && event.type !== inputType){
          return;
        }
        inputType = event.type;

        // Convert the canvas coordinate to physics coordinates
        var position = getPhysicsCoord(event);

        // Check if the mouse clicked the ball body using hitTest
        var didHitBall = world.hitTest(position, [ballBody]).length !== 0;

        if(didHitBall){

          var count = world.gravity[1] === 0 ? 0 : parseInt(counter.innerHTML) + 1;
          counter.innerHTML = count;

          // Apply an impulse on the ball
          var applyPoint = [0,0];
          var dx = ballBody.position[0] - position[0];
          var dy = 2;
          var len = Math.sqrt(dx*dx + dy*dy);
          var impulseSize = 15 + count / 3;
          var impulse = [
            dx / len * impulseSize,
            dy / len * impulseSize
          ];
          ballBody.applyImpulse(impulse, applyPoint);

          // Increase difficulty by increasing gravity!
          world.gravity[1] = - 10 - count;
        }
      }

      // Sets gravity to zero and the ball in initial position
      function resetGame(){
        world.gravity[0] = world.gravity[1] = 0;
        ballBody.position[0] = 0;
        ballBody.position[1] = -2;
        ballBody.velocity[0] = ballBody.velocity[1] = 0;
      }

      // Convert a canvas coordiante to physics coordinate
      function getPhysicsCoord(mouseEvent){
          var rect = canvas.getBoundingClientRect();
          var clientX = mouseEvent.touches ? mouseEvent.touches[0].clientX : mouseEvent.clientX;
          var clientY = mouseEvent.touches ? mouseEvent.touches[0].clientY : mouseEvent.clientY;
          var x = (clientX - rect.left) * window.devicePixelRatio;
          var y = (clientY - rect.top) * window.devicePixelRatio;

          x = (x - w / 2) / zoom;
          y = -(y - h / 2) / zoom;

          return [x, y];
      }

      // Creates a physics plane at a given position
      function createPlane(position, angle){
        var planeBody = new p2.Body({
          position: position,
          angle: angle
        });
        planeBody.addShape(new p2.Plane());
        world.addBody(planeBody);
        return planeBody;
      }

      // Animation loop
      var lastTime;
      var maxSubSteps = 5; // Max physics ticks per render frame
      var fixedDeltaTime = 1 / 30; // Physics "tick" delta time
      function animate(time){
        requestAnimationFrame(animate);

        // Get the elapsed time since last frame, in seconds
        var deltaTime = lastTime ? (time - lastTime) / 1000 : 0;

        // Make sure the time delta is not too big (can happen if user switches browser tab)
        deltaTime = Math.min(1 / 10, deltaTime);

        // Move physics bodies forward in time
        world.step(fixedDeltaTime, deltaTime, maxSubSteps);

        lastTime = time;

        // Render scene
        render();
      }

      function render(){

        // Clear whole drawing area
        ctx.fillRect(
          -gameWidth, -gameHeight,
          gameWidth*2, gameHeight*2
        );

        // Draw the ball
        // We choose to render the interpolated position to get smooth animation.
        ctx.beginPath();
        ctx.arc(
          ballBody.interpolatedPosition[0],
          ballBody.interpolatedPosition[1],
          ballRadius,
          0,
          2*Math.PI
        );
        ctx.fill();
        ctx.stroke();

        // Draw the outer rectangle
        ctx.beginPath();
        ctx.moveTo(-gameWidth/2,-gameHeight/2);
        ctx.lineTo(gameWidth/2,-gameHeight/2);
        ctx.lineTo(gameWidth/2,gameHeight/2);
        ctx.lineTo(-gameWidth/2,gameHeight/2);
        ctx.lineTo(-gameWidth/2,-gameHeight/2);
        ctx.stroke();
      }

    </script>

  </body>
</html>
